Перейти к основному содержимому

5.02. Переменные

Разработчику Архитектору

Переменные

Важно понять, что в большинстве языков программирования переменная является «ящиком», в котором хранится значение. В Python — это ссылка на объект. То есть, переменная лишь имя, которое указывает (ссылается) на объект в памяти.

Когда мы пишем:

x = 42

…то мы не сохраняем 42 в x, а говорим интерпретатору создать объект типа int со значением 42 и связать с ним имя x. То есть, переменная не имеет типа, тип есть у объекта, на который она ссылается. Одна и та же переменная может ссылаться на объекты разных типов, и несколько переменных могут ссылаться на один и тот же объект.

a = [1, 2, 3]
b = a # b теперь ссылается на тот же список
b.append(4)
print(a) # [1, 2, 3, 4] — изменился и a!

a и b - две ссылки на один и тот же изменяемый объект.

В Python нет ключевых слов для объявления переменных. Нет ничего вроде var, let, const, int, str и т.п.

Объявление переменной происходит в момент первого присваивания:

name = "Алексей"        # переменная создана
age = 30 # ещё одна
is_student = False # и ещё одна

Никаких предварительных объявлений — интерпретатор сам добавляет имя в текущее пространство имён.

Литерал — это способ записи значения прямо в коде. Они используются при присвоении переменных:

number = 42              # 42 — целочисленный литерал
pi = 3.14159 # литерал float
text = "Hello" # строковый литерал
flag = True # булев литерал
data = [1, 2, 3] # литерал списка
config = {"db": "prod"} # литерал словаря
point = (10, 20) # литерал кортежа
binary = b"hello" # байтовый литерал

Литералы — это «сырые» значения, которые создаются как объекты в памяти, и на них можно ссылаться через переменные. Как уже упоминалось, Python — динамически типизированный язык. Это означает, что тип переменной определяется во время выполнения, и она может менять тип в процессе работы программы.

value = 100       # int
value = "текст" # str
value = [1, 2] # list

Поскольку переменные — это ссылки, важно различать равенство значений (==) и идентичность объекта (is).

a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(a == b) # True — значения одинаковые
print(a is b) # False — разные объекты в памяти
print(a is c) # True — ссылаются на один объект

Функция id() показывает уникальный идентификатор объекта:

print(id(a))  # например, 140234567890123
print(id(b)) # другой адрес
print(id(c)) # такой же, как у a

Области видимости переменных (Scopes). Python следует правилу LEGB, определяющему, где искать переменную при обращении к ней:

  • L - Local, внутри функции;
  • E - Enclosing - в обрамляющих функциях (замыканиях);
  • G - Global - на уровне модуля;
  • B - Built-in - встроенные имена (len, print, True).

Пример:

x = "глобальный"

def outer():
x = "обрамляющий"

def inner():
x = "локальный"
print(x) # -> "локальный"

inner()
print(x) # -> "обрамляющий"

outer()
print(x) # -> "глобальный"

Локальные переменные создаются внутри функции и доступны только внутри неё.

def greet():
message = "Привет!" # локальная переменная
print(message)

greet()
# print(message) # Ошибка! NameError

Глобальные переменные объявлены на уровне модуля (вне функций/классов):

counter = 0

def increment():
global counter # говорим, что хотим использовать глобальную переменную
counter += 1

increment()
print(counter) # 1

Без global попытка изменить counter создаст локальную переменную с тем же именем.

Аналогично — nonlocal для доступа к переменным из обрамляющей области:

def outer():
x = "внешний"

def inner():
nonlocal x
x = "изменили изнутри"

inner()
print(x) # "изменили изнутри"

Поэтому в Python, как и в JavaScript, есть лексическое окружение и замыкания (closures).

Замыкание — функция, которая запоминает значения из обрамляющей области, даже после завершения этой области.

def make_multiplier(n):
def multiplier(x):
return x * n # n — из обрамляющей области
return multiplier

double = make_multiplier(2)
triple = make_multiplier(3)

print(double(5)) # 10
print(triple(5)) # 15

Здесь multiplier — замыкание, которое «захватывает» значение n. Атрибут .__closure__ позволяет посмотреть захваченные переменные:

print(double.__closure__[0].cell_contents)  # 2

Это мощный механизм, используемый в декораторах, каррировании и фабриках функций. Но о функциях позже.

Python поддерживает соглашения об именах, влияющих на поведение. Мы уже о них говорили - это _ для временной переменной, __name__ для специальных переменных модуля.

Кроме этого, в Python нет настоящих констант, но есть соглашение - имена в верхнем регистре считаются константами:

PI = 3.14159
MAX_RETRIES = 3
DEFAULT_TIMEOUT = 30

Это не защищает от изменения, но сигнализирует, что значение не должно меняться.

PI = 3  # технически возможно, но плохо по стилю

Для настоящей защиты можно использовать модуль types.MappingProxyType (неизменяемый словарь), классы с @property или сторонние библиотеки вроде const.

from types import MappingProxyType

CONFIG = {
'API_URL': 'https://api.example.com',
'TIMEOUT': 30
}

READONLY_CONFIG = MappingProxyType(CONFIG)
# READONLY_CONFIG['API_URL'] = '...' # Ошибка!

И разумеется, нельзя использовать ключевые слова как имена:

# if = 5        # SyntaxError
# class = "A" # SyntaxError

Список ключевых слов можно получить:

import keyword
print(keyword.kwlist)
# ['False', 'None', 'True', 'and', 'as', 'assert', ...]

В именах разрешены буквы, цифры, подчёркивание. Начинать следует с буквы или нижнего подчёркивания (_), но не с цифры. А юникод-символы лучше избегать, хотя можно.

Стиль написания в Python именно snake_case, стандарт PEP 8, а имена описывать лучше так: user_name, total_price, is_active.

Имеется множественное присваивание:

a, b = 1, 2
x, y, z = "X", "Y", "Z"

С распаковкой:

first, *rest = [1, 2, 3, 4]
# first = 1, rest = [2, 3, 4]

Обмен значениями:

a, b = b, a  # без временной переменной

Удаление переменной:

x = 100
del x # имя x удаляется из пространства имён
# print(x) # NameError

del удаляет ссылку, а не объект. Объект удалится сборщиком мусора, когда на него не останется ссылок.

Проверка существования переменной:

if 'my_var' in globals():
print("Есть такая глобальная переменная")

# Или через getattr, hasattr — особенно для объектов

Python использует пространства имён — словари, где ключи — имена, значения — объекты.

Виды:

  • Локальное — locals()
  • Глобальное — globals()
  • Встроенное — dir(builtins)
x = 10

def func():
y = 20
print(locals()) # {'y': 20}
print(globals()['x']) # 10

func()

Эти функции полезны для метапрограммирования, но редко нужны в обычном коде.